package com.job.client.auth.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.job.client.auth.service.RecordService;
import com.job.client.common.exception.MyselfException;
import com.job.client.common.minio.BucketNameConstant;
import com.job.client.common.minio.MinIOUtils;
import com.job.client.common.result.AppHttpCode;
import com.job.client.common.result.ResponseResult;
import com.job.client.common.utils.*;
import com.job.client.auth.mapper.AuthMapper;
import com.job.client.auth.service.AuthService;
import com.job.client.model.auth.constant.ActionConstant;
import com.job.client.model.auth.constant.MinioConstant;
import com.job.client.model.rabbitmq.AuditConstant;
import com.job.client.model.auth.constant.RedisConstant;
import com.job.client.model.auth.pojo.User;
import com.job.client.model.rabbitmq.AuthConstant;
import com.job.feign.client.company.CompanyFeign;
import com.job.feign.client.message.MessageFeign;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.amqp.rabbit.core.RabbitTemplate;

@Service
@Slf4j
public class AuthServiceImpl extends ServiceImpl<AuthMapper, User> implements AuthService {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    @Autowired
    private AuthMapper authMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private CompanyFeign companyFeign;
    @Autowired
    private MessageFeign messageFeign;
    @Autowired
    private RecordService recordService;
    public ResponseResult getImgCode(HttpServletResponse response, HttpServletRequest request){
        // 获取到session
        HttpSession session = request.getSession();
        // 取到sessionid
        String id = session.getId();
        // 是否有存储过
        Object data = redisTemplate.opsForValue().get(RedisConstant.IMGCODE_STORAGE_PREFIX + id);
        if(data!=null && "0".equals(data)){
            return ResponseResult.ErrorResult("图形验证码次数耗尽，请过五分钟再尝试",201);
        }
        // 利用图片工具生成图片
        // 返回的数组第一个参数是生成的验证码，第二个参数是生成的图片
        Object[] objs = ImgCodeUtil.newBuilder()
                .setWidth(120)   //设置图片的宽度
                .setHeight(35)   //设置图片的高度
                .setSize(4)      //设置字符的个数
                .setLines(10)    //设置干扰线的条数
                .setFontSize(25) //设置字体的大小
                .setTilt(true)   //设置是否需要倾斜
                .setBackgroundColor(Color.LIGHT_GRAY) //设置验证码的背景颜色
                .build()         //构建VerifyUtil项目
                .createImage();  //生成图片
        // 将验证码存入Session
        session.setAttribute(RedisConstant.IMGCODE_SESSION_STORAGE_PREFIX + id, objs[0]);
        log.info("生成图形验证码："+objs[0]);
        // 设置redis值的序列化方式
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        // 在redis中保存一个验证码最多尝试次数
        // 这里采用的是先预设一个上限次数，再以reidis decrement(递减)的方式来进行验证
        // 这样有个缺点，就是用户只申请验证码，不验证就走了的话，这里就会白白占用5分钟的空间，造成浪费了
        // 为了避免以上的缺点，也可以采用redis的increment（自增）方法，只有用户开始在做验证的时候设置值，
        //    超过多少次错误，就失效；避免空间浪费
        String num="5";
        if(data!=null){
            num= (String) data;
        }
        redisTemplate.opsForValue().set((RedisConstant.IMGCODE_STORAGE_PREFIX + id), num, 5 * 60, TimeUnit.SECONDS);

        // 将图片输出给浏览器
        BufferedImage image = (BufferedImage) objs[1];
        String encodedImage="";
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, "png", baos);
            baos.flush();
            byte[] imageBytes = baos.toByteArray();
            encodedImage = Base64.getEncoder().encodeToString(imageBytes);
            baos.close();
        }catch (IOException e){
            e.printStackTrace();
            new MyselfException(201,"输出图形的BASE64编码到前端出错",e);
        }
        if(encodedImage.equals("")){
            return ResponseResult.ErrorResult("输出图形的BASE64编码到前端出错",201);
        }else{
            return ResponseResult.OkResult(encodedImage);
        }

    }

    @Override
    public ResponseResult checkImgCode(String code, HttpServletRequest request) {
        HttpSession session = request.getSession();
        String id = session.getId();
        // 将redis中的尝试次数减一
        String verifyCodeKey = RedisConstant.IMGCODE_STORAGE_PREFIX + id;

        Object data = redisTemplate.opsForValue().get(verifyCodeKey);
        // 如果次数次数小于1 说明验证码已经失效
        if ("1".equals(data)) {
            String clientIP = request.getRemoteAddr();
            log.warn(clientIP+"：图形验证码失败");
            return ResponseResult.ErrorResult("图形验证码次数耗尽，请过五分钟再尝试",201);
        }
        long num = redisTemplate.opsForValue().decrement(verifyCodeKey);
        // 将session中的取出对应session id生成的验证码
        String serverCode = (String) session.getAttribute(RedisConstant.IMGCODE_SESSION_STORAGE_PREFIX + id);
        // 校验验证码
        if (null == serverCode || null == code || !serverCode.toUpperCase().equals(code.toUpperCase())) {
            return ResponseResult.ErrorResult("图形验证码错误，剩余次数："+num,201);
        }
        // 验证通过之后手动将验证码失效
        redisTemplate.delete(verifyCodeKey);
        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult sendSmsCode(String phone, HttpServletRequest request) {
        // phone验证
        if(phone==null){
            return ResponseResult.ErrorResult(AppHttpCode.PHONE_ERROR);
        }
        Pattern pattern = Pattern.compile("^1[3-9]\\d{9}$");
        Matcher matcher = pattern.matcher(phone);
        if(!matcher.matches()){
            return ResponseResult.ErrorResult(AppHttpCode.PHONE_ERROR);
        }
        String smsCode = null;
//        try {
//            smsCode = SendSmsToAly.sendSmsCode(phone);
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }
        smsCode = SendSmsToAly.sendSmsCodeExample(phone);
        log.info("生成了短信验证码："+smsCode);
        if(smsCode==null){
            return ResponseResult.ErrorResult(AppHttpCode.ALY_SMS_ERROR);
        }
        // 删除之前存储在redis的数据
        redisTemplate.delete(RedisConstant.SMSCODE_STORAGE_PREFIX+phone);
        // 获取到session
        HttpSession session = request.getSession();
        // 把验证码保存在会话中
        session.setAttribute(RedisConstant.SMSCODE_SESSION_STORAGE_PREFIX + phone, smsCode);
        // 把次数存储到redis中
        redisTemplate.opsForValue().set((RedisConstant.SMSCODE_STORAGE_PREFIX + phone), RedisConstant.SMSCODE_NUM, 5 * 60, TimeUnit.SECONDS);
        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult checkSmsCode(@RequestParam String code,@RequestParam String phone, HttpServletRequest request) {
        // 匹配code
        if(code==null){
            return ResponseResult.ErrorResult(AppHttpCode.SMS_CODE_NULL);
        }
        String regex = "\\d{4}";
        Pattern pattern_code = Pattern.compile(regex);
        Matcher matcher_code = pattern_code.matcher(code);
        if(!matcher_code.matches()){
            return ResponseResult.ErrorResult(AppHttpCode.SMS_CODE_NULL);
        }
        // phone验证
        if(phone==null){
            return ResponseResult.ErrorResult(AppHttpCode.PHONE_ERROR);
        }
        Pattern pattern_phone = Pattern.compile("^1[3-9]\\d{9}$");
        Matcher matcher_phone = pattern_phone.matcher(phone);
        if(!matcher_phone.matches()){
            return ResponseResult.ErrorResult(AppHttpCode.PHONE_ERROR);
        }

        HttpSession session = request.getSession();
        // 将redis中的尝试次数减一
        String smsCodeKey = RedisConstant.SMSCODE_STORAGE_PREFIX + phone;
        // 校验是否已经发送了验证码
        Object o = redisTemplate.opsForValue().get(smsCodeKey);
        if(o==null){
            return ResponseResult.ErrorResult(AppHttpCode.SMS_TIMEOUT_NOTSEND);
        }
        long num = redisTemplate.opsForValue().decrement(smsCodeKey);
        // 如果次数次数小于0 说明验证码已经失效
        if (num <= 0) {
            String clientIP = request.getRemoteAddr();
            log.warn(clientIP+"：手机验证码失败");
            redisTemplate.delete(RedisConstant.SMSCODE_STORAGE_PREFIX + phone);
            return ResponseResult.ErrorResult("短信验证码次数耗尽，请重新发送验证码",201);
        }
        // 将session中的取出对应session id生成的验证码
        String smsCode = (String) session.getAttribute(RedisConstant.SMSCODE_SESSION_STORAGE_PREFIX +phone);
        // 校验验证码
        if (null == smsCode || null == code || !smsCode.equals(code)) {
            return ResponseResult.ErrorResult("短信验证码错误，剩余次数："+num,201);
        }
        // 验证通过之后手动将验证码失效
        redisTemplate.delete(RedisConstant.SMSCODE_STORAGE_PREFIX + phone);
        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult register(User user) {
        // 验证数据
        if(user==null || StringUtils.isNullOrEmpty(user.getUsername()) ||
                StringUtils.isNullOrEmpty(user.getPassword()) ||
                StringUtils.isNullOrEmpty(user.getPhone())){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        //  判断用户名、手机号是否存在
        QueryWrapper<User> userQuery=new QueryWrapper<>();
        userQuery.eq("phone",user.getPhone());
        User selectOne = authMapper.selectOne(userQuery);
        if(selectOne!=null){
            return ResponseResult.ErrorResult(AppHttpCode.PHONE_EXIST);
        }
        user.setCreateTime(DateTimeUtils.createNowTime());
        user.setUpdateTime(DateTimeUtils.createNowTime());
        user.setPassword(PasswordUtil.encrypt(user.getPassword()));  // md5加盐的不可逆加密
        user.setAvatarImage(MinioConstant.AVATAR_IMAGE_URL+MinioConstant.AVATAR_DEFAULT);
        int i = authMapper.insert(user);
        if(i==1){
            messageFeign.createMessage(user.getUserId());
            recordService.initRecord(user.getUserId());
            return ResponseResult.OkResult();
        }
        return ResponseResult.ErrorResult(AppHttpCode.SERVICE_ERROR);
    }

    @Override
    public ResponseResult login(User user) {
        log.warn("用户id,登陆了客户端");
        // 验证user
        if(user==null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        User selectOne=null;
        if(!StringUtils.isNullOrEmpty(user.getPhone())){
            QueryWrapper<User> phoneQuery = new QueryWrapper<>();
            phoneQuery.eq("phone",user.getPhone());
            selectOne = authMapper.selectOne(phoneQuery);
            if(selectOne == null){
                return ResponseResult.ErrorResult(AppHttpCode.PHONE_NOT_EXIST);
            }
        }else if(!StringUtils.isNullOrEmpty(user.getUsername()) && !StringUtils.isNullOrEmpty(user.getPassword())){
            QueryWrapper<User> usernameQuery = new QueryWrapper<>();
            usernameQuery.eq("username",user.getUsername());
            selectOne = authMapper.selectOne(usernameQuery);
            if(selectOne==null){
                return ResponseResult.ErrorResult(AppHttpCode.USERNAME_NOT_EXIST);
            }
            // 匹配是否和数据库存储的加密的密码一致
            boolean valid = PasswordUtil.valid(user.getPassword(), selectOne.getPassword());
            if(!valid){
                return ResponseResult.ErrorResult(AppHttpCode.PASSWORD_ERROR);
            }
        }else{
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
//        发送rabbitmq记录用户登录/退出日志
        Map<String,String> data = new HashMap<>();
        data.put("userId",selectOne.getUserId());
        data.put("actionType", String.valueOf(ActionConstant.ACTION_LOGIN));
        rabbitTemplate.convertAndSend(AuthConstant.EXCHANGE_AUTH,AuthConstant.ROUTINGKEY_ACTION,JSON.toJSONString(data));

        String token = JwtTokenUtils.createToken(selectOne, 1);
        return ResponseResult.OkResult(token);
    }

    @Override
    public ResponseResult getUserInfo(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        System.out.println(token);
        // token为”“或者null则未登录
        if(StringUtils.isNullOrEmpty(token)){
            return ResponseResult.ErrorResult(AppHttpCode.LOGIN_AUTH);
        }
        User user = JwtTokenUtils.checkToken(token);
        // 其中有数据缺失则表示token被修改
        if(user.getUserId()==null || (user.getUsername()==null && user.getPhone()==null)){
            return ResponseResult.ErrorResult(AppHttpCode.TOKEN_ERROR);
        }
        return ResponseResult.OkResult(user);
    }

    public ResponseResult getUserInfoStr(String token) {
        // token为”“或者null则未登录
        if(StringUtils.isNullOrEmpty(token)){
            return ResponseResult.ErrorResult(AppHttpCode.LOGIN_AUTH);
        }
        User user = JwtTokenUtils.checkToken(token);
        // 其中有数据缺失则表示token被修改
        if(user.getUserId()==null || (user.getUsername()==null && user.getPhone()==null)){
            return ResponseResult.ErrorResult(AppHttpCode.TOKEN_ERROR);
        }
        return ResponseResult.OkResult(user);
    }

    @Override
    public ResponseResult uploadAvatar(MultipartFile file,String Authorization) {
        if(file==null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        String[] split = file.getOriginalFilename().split("\\.");
        String contentType = split[split.length-1];
        String uuid = String.valueOf(UUID.randomUUID());  //使用uuid获得文件的扩展名
        String fileName = uuid+"."+contentType;
        try {
            MinIOUtils.uploadFile(BucketNameConstant.CLIENT_AVATAR,file,fileName,contentType);  //反斜杠是一个转义字符，所以需要使用两个反斜杠来表示一个实际的点
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            return ResponseResult.ErrorResult(AppHttpCode.MINIO_UPLOAD_FAIL);
        }
        String avatarURL = MinIOUtils.getBasisUrl(BucketNameConstant.CLIENT_AVATAR, fileName);
        // 使用rabbitmq提交到审核列表
        // 准备数据
        ResponseResult responseResult = getUserInfoStr(Authorization);
        if(responseResult.getCode()!=200){
            return ResponseResult.ErrorResult(AppHttpCode.FETCH_ACCESSTOKEN_FAILD);
        }
        User user = (User) responseResult.getData();
        String userId = user.getUserId();
        Map<String,String> data = new HashMap<>();
        data.put("userId",userId);
        data.put("avatarImage",avatarURL);

        // rabbitmq发送
        String dataJson = JSON.toJSONString(data);
        rabbitTemplate.convertAndSend(AuditConstant.EXCHANGE_AUDIT, AuditConstant.ROUTINGKEY_AVATAR,dataJson);
        return ResponseResult.OkResult(avatarURL);
    }

    @Override
    public ResponseResult updateAvatar(User user) {
        if(user == null || user.getUserId() ==null || user.getAvatarImage() == null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        UpdateWrapper<User> userUpdateWrapper=new UpdateWrapper<>();
        userUpdateWrapper
                .set("avatar_image",user.getAvatarImage())
                .set("update_time",DateTimeUtils.createNowTime())
                .eq("user_id",user.getUserId());
        int i = authMapper.update(userUpdateWrapper);
        if(i==1){
            return ResponseResult.OkResult();
        }
        return ResponseResult.ErrorResult(AppHttpCode.AVATAR_FAIL);
    }

    @Override
    public ResponseResult addDuties(String duties,HttpServletRequest request) {
        if(duties==null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        ResponseResult responseResult = getUserInfo(request);
        if(responseResult.getCode()!=200){
            return ResponseResult.ErrorResult(AppHttpCode.FETCH_ACCESSTOKEN_FAILD);
        }
        User user = (User) responseResult.getData();
        String userId = user.getUserId();
        UpdateWrapper<User> userUpdateWrapper=new UpdateWrapper<>();
        userUpdateWrapper.set("duties",duties).eq("user_id",userId);
        int i = authMapper.update(userUpdateWrapper);
        if(i!=1){
            return ResponseResult.ErrorResult(AppHttpCode.SERVICE_ERROR);
        }
        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult addCompany(String company, HttpServletRequest request) {
        if(company==null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        ResponseResult responseResult = getUserInfo(request);
        if(responseResult.getCode()!=200){
            return ResponseResult.ErrorResult(AppHttpCode.FETCH_ACCESSTOKEN_FAILD);
        }
        User user = (User) responseResult.getData();
        String userId = user.getUserId();
        UpdateWrapper<User> userUpdateWrapper=new UpdateWrapper<>();
        userUpdateWrapper.set("company_name",company).eq("user_id",userId);
        int i = authMapper.update(userUpdateWrapper);
        if(i!=1){
            return ResponseResult.ErrorResult(AppHttpCode.SERVICE_ERROR);
        }
        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult getUserInfoById(String userId) {
        if(userId==null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        User user = authMapper.selectById(userId);
        if(user==null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_ERROR);
        }
        return ResponseResult.OkResult(user);
    }

    @Override
    public ResponseResult authentication(String companyId,String userId,String companyName) {
        if(companyId == null || userId == null || companyName == null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper
                .set("company_id",companyId)
                .set("authentication",1)
                .set("update_time",DateTimeUtils.createNowTime())
                .set("company_name",companyName)
                .eq("user_id",userId);
        int i = authMapper.update(updateWrapper);
        if(i!=1){
            return ResponseResult.ErrorResult(AppHttpCode.SERVICE_ERROR);
        }
        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult logout(String userId) {
        if(userId == null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        Map<String,String> data = new HashMap<>();
        data.put("userId",userId);
        data.put("actionType", String.valueOf(ActionConstant.ACTION_LOGOUT));
        rabbitTemplate.convertAndSend(AuthConstant.EXCHANGE_AUTH,AuthConstant.ROUTINGKEY_ACTION,JSON.toJSONString(data));

        return ResponseResult.OkResult();
    }

    @Override
    public ResponseResult getCompanyByUserId(String userId) {
        if(userId == null){
            return ResponseResult.ErrorResult(AppHttpCode.DATA_NULL);
        }
        User user = authMapper.selectById(userId);
        if(user == null){
            return ResponseResult.ErrorResult(AppHttpCode.SERVICE_ERROR);
        }
        if(user.getCompanyId() == null || "".equals(user.getCompanyId())){
            return ResponseResult.OkResult();
        }
        ResponseResult result = companyFeign.getCompanyById(user.getCompanyId());
        if(result.getCode() != 200){
            return result;
        }
        return ResponseResult.OkResult(result.getData());
    }

}
